using System.Diagnostics.CodeAnalysis;
using System.Text;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;

namespace LyxWaf.Services.Files;

public interface IFileService
{
    Task<string?> GetFileRealAsync(string host, string prefix, string path);
    Task<FileServiceResult> GetFileAsync(string host, string prefix, string path, RangeHeaderValue? range = null);
    Task<FileMetadata> GetFileMetadataAsync(string path);
}
public class FileService : IFileService
{
    private FileProviderOptions _options;
    private readonly IMemoryCache _cache;
    private readonly ILogger<FileService> _logger;

    public FileService(
        IOptionsMonitor<FileProviderOptions> options, IMemoryCache cache,
        ILogger<FileService> logger)
    {
        _options = options.CurrentValue;
        // 可以订阅变更，但需注意生命周期和内存泄漏
        options.OnChange(newConfig =>
        {
            _options = newConfig;
        });
        _cache = cache;
        _logger = logger;
    }

    private string? TryGetConfig(string host, string prefix, [MaybeNullWhen(false)] out FileEveryConfig? config)
    {

        string key = $"{host}#{prefix}";
        if (_options.Everys.TryGetValue(key, out config))
        {
            return key;
        }
        if (_options.Everys.TryGetValue(prefix, out config))
        {
            return prefix;
        }
        return null;
    }

    public async Task<string?> GetFileRealAsync(string host, string prefix, string path)
    {
        var key = TryGetConfig(host, prefix, out var val);
        if (key == null)
        {
            return null;
        }

        var safePath = GetSafePath(val!.BasePath, path);
        var cacheKey = $"exists_{key}_{safePath}";
        return await _cache.GetOrCreateAsync(cacheKey, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = _options.CacheDuration;
            if (val.TryFiles != null)
            {
                foreach (var f in val.TryFiles)
                {
                    var v = f.Replace("$path", path);
                    var safePath = GetSafePath(val.BasePath, v);

                    if (File.Exists(safePath))
                    {
                        return safePath;
                    }

                    if (Directory.Exists(safePath))
                    {
                        foreach (var def in val.Default)
                        {
                            var subPath = GetSafePath(safePath, def);
                            if (File.Exists(subPath))
                            {
                                return subPath;
                            }
                        }
                    }
                }
                return null;
            }
            else
            {
                // 空引导则寻找Default的值
                if (path.Length == 0)
                {
                    foreach (var def in val.Default)
                    {
                        var safePath = GetSafePath(val.BasePath, def);
                        if (File.Exists(safePath))
                        {
                            return safePath;
                        }
                    }
                    return null;
                }
                else
                {
                    return File.Exists(safePath) ? safePath : null;
                }
            }
        });
    }

    public async Task<FileServiceResult> GetFileAsync(string host, string prefix, string real, RangeHeaderValue? range = null)
    {
        if (!File.Exists(real))
            return FileServiceResult.NotFound();

        var fileInfo = new FileInfo(real);
        var metadata = await GetFileMetadataAsync(real);
        if (metadata.IsNotSupport())
        {
            return FileServiceResult.NoSupportContentType();
        }
        var key = TryGetConfig(host, prefix, out var val);
        if (key == null)
        {
            return FileServiceResult.NotFound();
        }

        if (val!.MaxFileSize * 1024 < metadata.Size)
        {
            return FileServiceResult.FileToBigger();
        }

        if (range != null)
        {
            return await HandleRangeRequest(fileInfo, range, metadata);
        }

        return await HandleFullFileRequest(fileInfo, metadata);
    }

    private static async Task<FileServiceResult> HandleFullFileRequest(FileInfo fileInfo, FileMetadata metadata)
    {
        var fileStream = new FileStream(fileInfo.FullName,
            FileMode.Open, FileAccess.Read, FileShare.Read, 4096,
            FileOptions.Asynchronous | FileOptions.SequentialScan);

        return new FileServiceResult
        {
            Stream = fileStream,
            ContentType = metadata.ContentType,
            FileLength = fileInfo.Length,
            LastModified = fileInfo.LastWriteTimeUtc,
            ETag = metadata.ETag,
            StatusCode = StatusCodes.Status200OK
        };
    }

    private static async Task<FileServiceResult> HandleRangeRequest(FileInfo fileInfo,
        RangeHeaderValue range, FileMetadata metadata)
    {
        long start = 0, end = fileInfo.Length - 1;

        if (range.Ranges.Count > 0)
        {
            var rangeItem = range.Ranges.First();
            if (rangeItem.From.HasValue)
            {
                start = rangeItem.From.Value;
                end = rangeItem.To ?? fileInfo.Length - 1;
            }
            else if (rangeItem.To.HasValue)
            {
                start = fileInfo.Length - rangeItem.To.Value;
                end = fileInfo.Length - 1;
            }
        }

        // 边界检查
        if (start < 0) start = 0;
        if (end >= fileInfo.Length) end = fileInfo.Length - 1;
        if (start > end) start = end;

        var contentLength = end - start + 1;
        var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
        fileStream.Seek(start, SeekOrigin.Begin);

        return new FileServiceResult
        {
            Stream = new LimitedStream(fileStream, start, contentLength),
            ContentType = metadata.ContentType,
            FileLength = contentLength,
            TotalLength = fileInfo.Length,
            LastModified = fileInfo.LastWriteTimeUtc,
            ETag = metadata.ETag,
            StatusCode = StatusCodes.Status206PartialContent,
            RangeStart = start,
            RangeEnd = end
        };
    }

    public async Task<FileMetadata> GetFileMetadataAsync(string real)
    {
        var cacheKey = $"metadata_{real}";

        var meta = await _cache.GetOrCreateAsync(cacheKey, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = _options.CacheDuration;

            var fileInfo = new FileInfo(real);
            return new FileMetadata
            {
                ContentType = GetContentType(fileInfo.Extension),
                LastModified = fileInfo.LastWriteTimeUtc,
                Size = fileInfo.Length,
                ETag = GenerateETag(fileInfo)
            };
        });
        return meta ?? throw new FileNotFoundException("文件不存在");
    }

    private static string GetSafePath(string basePath, string requestedPath)
    {
        if (requestedPath == "/")
        {
            return Path.GetFullPath(basePath);
        }
        var fullPath = Path.GetFullPath(Path.Combine(basePath, requestedPath));
        if (!fullPath.StartsWith(Path.GetFullPath(basePath)))
            throw new UnauthorizedAccessException("Access denied");

        return fullPath;
    }

    private string GetContentType(string extension)
    {
        extension = extension.ToLower();
        if (_options.MimeExtensions.TryGetValue(extension, out var val))
        {
            return val;
        }

        if (_options.RemoveExtensions.Contains(extension))
        {
            return FileMetadata.UnspportContentType;
        }

        return extension switch
        {
            ".jpg" or ".jpeg" => "image/jpeg",
            ".png" => "image/png",
            ".gif" => "image/gif",
            ".pdf" => "application/pdf",
            ".txt" => "text/plain",
            ".html" => "text/html",
            ".css" => "text/css",
            ".js" => "application/javascript",
            ".json" => "application/json",
            ".zip" => "application/zip",
            ".mp4" => "video/mp4",
            ".mp3" => "audio/mpeg",
            _ => "application/octet-stream"
        };
    }

    private static string GenerateETag(FileInfo fileInfo)
    {
        var info = $"{fileInfo.LastWriteTimeUtc.Ticks}_{fileInfo.Length}";
        return Convert.ToBase64String(Encoding.UTF8.GetBytes(info))
            .Replace("+", "-")
            .Replace("/", "_")
            .Replace("=", "");
    }
}